package cn.micro.core.util;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import java.beans.Introspector;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.Set;
import java.util.function.Function;

public class Validator<T> {

    private static final javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    private final T t;

    private final LinkedList<ConstraintViolation<T>> constraintViolations = new LinkedList<>();

    private Validator(T t) {
        this.t = t;
    }

    public static <T> Validator<T> in(T t) {
        return new Validator<>(t);
    }

    public Validator<T> valid(Class<?>... groups) {
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(this.t, groups);
        if (constraintViolations.size() > 0) {
            this.constraintViolations.addAll(constraintViolations);
        }
        return this;
    }

    public <P> Validator<T> valid(Property<T, P> property, Class<?>... groups) {
        String propertyName = property.name();
        Set<ConstraintViolation<T>> constraintViolations = validator.validateProperty(this.t, propertyName, groups);
        if (constraintViolations.size() > 0) {
            this.constraintViolations.addAll(constraintViolations);
        }
        return this;
    }

    @SuppressWarnings("unchecked")
    public <P> Validator<T> valid(Property<T, P> property, Object value, Class<?>... groups) {
        String propertyName = property.name();
        Class<T> clazz = (Class<T>) this.t.getClass();
        Set<ConstraintViolation<T>> constraintViolations = validator.validateValue(clazz, propertyName, value, groups);
        if (constraintViolations.size() > 0) {
            this.constraintViolations.addAll(constraintViolations);
        }
        return this;
    }

    public Validator<T> throwError(ValidConstraint<T> validConstraint) {
        ConstraintViolation<T> constraintViolation = this.constraintViolations.pollLast();
        if (null != constraintViolation) {
            validConstraint.error(constraintViolation);
        }
        return this;
    }

    public void throwErrors(ValidConstraint<T> validConstraint) {
        ConstraintViolation<T> constraintViolation = this.constraintViolations.poll();
        do {
            if (null != constraintViolation) {
                validConstraint.error(constraintViolation);
            }
            constraintViolation = this.constraintViolations.poll();
        } while (null != constraintViolation);
    }

    @FunctionalInterface
    public interface Property<T, R> extends Function<T, R>, Serializable {

        default String name() {
            Class<?> clazz = this.getClass();
            String name = null;
            try {
                Method method = clazz.getDeclaredMethod("writeReplace");
                method.setAccessible(Boolean.TRUE);
                SerializedLambda serializedLambda = (SerializedLambda) method.invoke(this);
                String getter = serializedLambda.getImplMethodName();
                name = Introspector.decapitalize(getter.replace("get", ""));
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                //throw new RuntimeException(e);
            }
            return name;
        }

    }

    @FunctionalInterface
    public interface ValidConstraint<T> {
        void error(ConstraintViolation<T> constraintViolation);
    }

}
